package me.shiqiang.im.utils;

import java.security.InvalidParameterException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 根据twitter雪花ID生成器思想编写的分布式ID生成器,可以生成趋势递增的ID，依赖每台实例的系统时间，所以在很短时间内所有实例生成的ID并不是
 * 严格意义上的递增，如果对生成的ID要求严格保证递增，慎用。
 *
 * @author cuishiqiang
 * @date 2018/1/12 下午4:04
 */
public class SnowFlake {

    public SnowFlake(long machineId, long serverId) {
        if (machineId > MACHINE_MAX_NUM) {
            throw new InvalidParameterException("machineId不可以大于机器最大数：" + MACHINE_MAX_NUM);
        }
        if (serverId > SERVER_MAX_NUM) {
            throw new InvalidParameterException("serverId不可以大于服务器最大数：" + SERVER_MAX_NUM);
        }
        this.machineId = machineId;
        this.serverId = serverId;
    }

    /** 起始时间戳 */
    private static final long start_ts = 1514736000000L;// 2018 0101

    private static final long MACHINE_BIT = 5L;
    private static final long SERVER_BIT = 5L;
    private static final long SEQ_BIT = 12L;

    /**
     * 获取每段的最大值
     * 拿int类型举例（long类型原理一致）
     * -1 二进制表示 11111111 11111111 11111111 11111111 共32位
     * -1 << 5 算数向左位移5位后  11111111 11111111 11111111 11100000
     * ~(-1 << 5) 对结果取反 00000000 00000000 00000000 00011111；11111 转换为10进制 2^6-1 = 31
     */
    private static final long MACHINE_MAX_NUM = ~(-1L << MACHINE_BIT);
    private static final long SERVER_MAX_NUM = ~(-1L << SERVER_BIT);
    private static final long SEQ_MAX_NUM = ~(-1L << SEQ_BIT);

    private static final long MACHINE_SHIFT = SEQ_BIT;
    private static final long SERVER_SHIFT = MACHINE_SHIFT + MACHINE_BIT;
    private static final long TS_SHIFT = SERVER_SHIFT + SERVER_BIT;

    private AtomicLong lastTs = new AtomicLong(System.currentTimeMillis());
    private AtomicInteger seq = new AtomicInteger(1);
    private long machineId;
    private long serverId;

    public long nextId() {
        long currentTs = System.currentTimeMillis();
        long ts = lastTs.getAndSet(currentTs);
        int currentSeq;
        if (currentTs == ts) {
            currentSeq = seq.getAndIncrement();
        } else {
            seq.set(2);
            currentSeq = 1;
        }
        if (currentSeq > SEQ_MAX_NUM) {
            throw new IndexOutOfBoundsException("");
        }
        return (currentTs-start_ts) << TS_SHIFT | serverId << SERVER_SHIFT | machineId << MACHINE_SHIFT | currentSeq;
    }
}
